<?php

/*
	info: ZIP操作类
	base: FileSystem
	$Id: zip.class.php 14 2011-08-29 08:48:19Z lfly1573@gmail.com $
*/

if (!defined('WEB_IN'))
{
	header("HTTP/1.0 404 Not Found");
	exit();
}

class Zip
{
	private $ctrldir = array();
	private $datasec = array();
	private $dirfile = array();
	private $dirs = array('.');
	private $eofctrldir = "\x50\x4b\x05\x06\x00\x00\x00\x00";
	private $oldoffset = 0;

	private $zipFile = '';


	/**
	 * 构造函数 可初始化zip文件
	 * @param $strZipFile 要处理的zip文件
	 * @return void
	 */
	public function __construct($strZipFile = '')
	{
		if ($strZipFile != '')
		{
			$this->zipFile = $strZipFile;
		}
	}

	/**
	 * 解压zip文件
	 * @param $strZipFile 要解压的zip文件
	 * @param $strToPath 解压到的目录
	 * @param $aryIndex 对应的index值(单独解压用，-1解压全部)
	 * @return array 文件解压结果数组
	 */
	public function ExtractZip($strZipFile = '', $strToPath = '.', $aryIndex = array('-1'))
	{
		if ($strZipFile == '')
		{
			$strZipFile = $this->zipFile;
		}
		$stat = array();
		if (substr($strToPath, -1)=='/')
		{
			$strToPath = substr($strToPath, 0, -1);
		}

		$zip = @fopen($strZipFile, 'rb');
		if (!$zip)
		{
			return array();
		}
		$cdir = $this->ReadCentralDir($zip, $strZipFile);
		$pos_entry = $cdir['offset'];

		for ($i=0; isset($aryIndex[$i]); $i++)
		{
			if (intval($aryIndex[$i])!=$aryIndex[$i] || $aryIndex[$i]>$cdir['entries'])
			{
				return array();
			}
		}

		for ($i=0; $i<$cdir['entries']; $i++)
		{
			@fseek($zip, $pos_entry);
			$header = $this->ReadCentralFileHeaders($zip);
			$header['index'] = $i;
			$pos_entry = ftell($zip);
			@rewind($zip);
			fseek($zip, $header['offset']);
			if (in_array('-1', $aryIndex) || in_array($i, $aryIndex))
			{
				if ($this->ExtractFile($strToPath, $zip))
				{
					$stat[] = $strToPath.'/'.$header['filename'];
				}
			}
		}
		fclose($zip);
		return $stat;
	}

	/**
	 * 生成zip压缩包
	 * @param $strZipFile 要生成的zip文件路径及名字
	 * @param $aryFile 要压缩的文件数组，每个文件数据包括原文件及新文件
	 * @return array 已经打包的文件数组
	 */
	public function MakeZip($strZipFile, $aryFile)
	{
		if (!empty($aryFile))
		{
			$this->dirfile = array();
			foreach ($aryFile as $tempfile)
			{
				if (is_dir($tempfile[0]))
				{
					$this->DirList($tempfile[0], $tempfile[1]);
				}
			}
			$resultAryFile = array_merge($aryFile, $this->dirfile);
			$inzipFile = $this->AddZip($resultAryFile);

			FileSystem::CreateFolder(dirname($strZipFile));
			if (@fwrite(@fopen($strZipFile, 'wb'), $this->JoinData()))
			{
				return $inzipFile;
			}
		}
		return array();
	}

	/**
	 * 查看zip文件列表
	 * @param $strZipFile 要查看的zip文件路径及名字
	 * @return array zip文件内文件夹和文件的数组
	 */
	public function ViewZip($strZipFile = '')
	{
		if ($strZipFile == '')
		{
			$strZipFile = $this->zipFile;
		}
		$zip = @fopen($strZipFile, 'rb');
		if (!$zip)
		{
			return FALSE;
		}
		$centd = $this->ReadCentralDir($zip, $strZipFile);

		@rewind($zip);
		@fseek($zip, $centd['offset']);

		for ($i=0; $i<$centd['entries']; $i++)
		{
			$header = $this->ReadCentralFileHeaders($zip);
			$header['index'] = $i;
			$info['filename'] = $header['filename'];
			$info['stored_filename'] = $header['stored_filename'];
			$info['size'] = $header['size'];
			$info['compressed_size'] = $header['compressed_size'];
			$info['crc'] = strtoupper(dechex($header['crc']));
			$info['mtime'] = $header['mtime'];
			$info['comment'] = $header['comment'];
			$info['folder'] = ($header['external']==0x41FF0010 || $header['external']==16) ? 1 : 0;
			$info['index'] = $header['index'];
			$info['status'] = $header['status'];
			$ret[] = $info;
			unset($header);
		}
		return $ret;
	}


	/***********************************************************************/


	/* 添加一个文件夹 */
	private function AddDir($strDirName)
	{
		$name = str_replace('\\', '/', $strDirName);
		$fr = "\x50\x4b\x03\x04\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00";

		$fr .= pack('V', 0).pack('V', 0).pack('V', 0).pack('v', strlen($name));
		$fr .= pack('v', 0).$name.pack('V', 0).pack('V', 0).pack('V', 0);
		$this->datasec[] = $fr;

		$new_offset = strlen(implode('', $this->datasec));

		$cdrec = "\x50\x4b\x01\x02\x00\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00";
		$cdrec .= pack('V',0).pack('V',0).pack('V',0).pack('v', strlen($name));
		$cdrec .= pack('v', 0).pack('v', 0).pack('v', 0).pack('v', 0);
		$ext = "\xff\xff\xff\xff";
		$cdrec .= pack('V', 16).pack('V', $this->oldoffset).$name;

		$this->ctrldir[] = $cdrec;
		$this->oldoffset = $new_offset;
		$this->dirs[] = $name;
	}

	/* 添加一个文件 */
	private function AddFile($strData, $strFileName, $intCompact = 1)
	{
		$name = str_replace('\\', '/', $strFileName);
		$dtime = dechex($this->DosTime());

		$hexdtime = '\x'.$dtime[6].$dtime[7].'\x'.$dtime[4].$dtime[5].'\x'.$dtime[2].$dtime[3].'\x'.$dtime[0].$dtime[1];
		eval('$hexdtime = "'.$hexdtime.'";');

		if ($intCompact)
		{
			$fr = "\x50\x4b\x03\x04\x14\x00\x00\x00\x08\x00".$hexdtime;
		}
		else
		{
			$fr = "\x50\x4b\x03\x04\x0a\x00\x00\x00\x00\x00".$hexdtime;
		}
		$unc_len = strlen($strData);
		$crc = crc32($strData);

		if ($intCompact)
		{
			$zdata = gzcompress($strData);
			$c_len = strlen($zdata);
			$zdata = substr(substr($zdata, 0, strlen($zdata)-4), 2);
		}
		else
		{
			$zdata = $strData;
		}
		$c_len = strlen($zdata);
		$fr .= pack('V', $crc).pack('V', $c_len).pack('V', $unc_len);
		$fr .= pack('v', strlen($name)).pack('v', 0).$name.$zdata;

		$fr .= pack('V', $crc).pack('V', $c_len).pack('V', $unc_len);

		$this->datasec[] = $fr;
		$new_offset = strlen(implode('', $this->datasec));
		if ($intCompact)
		{
			$cdrec = "\x50\x4b\x01\x02\x00\x00\x14\x00\x00\x00\x08\x00";
		}
		else
		{
			$cdrec = "\x50\x4b\x01\x02\x14\x00\x0a\x00\x00\x00\x00\x00";
		}
		$cdrec .= $hexdtime.pack('V', $crc).pack('V', $c_len).pack('V', $unc_len);
		$cdrec .= pack('v', strlen($name)).pack('v', 0).pack('v', 0);
		$cdrec .= pack('v', 0).pack('v', 0).pack('V', 32);
		$cdrec .= pack('V', $this->oldoffset);

		$this->oldoffset = $new_offset;
		$cdrec .= $name;
		$this->ctrldir[] = $cdrec;

		return TRUE;
	}

	/* 添加到压缩包 */
	private function AddZip($aryFiles, $intCompact = 1)
	{
		$ret = array();

		for ($i=0; isset($aryFiles[$i]); $i++)
		{
			$fn = $aryFiles[$i];
			if (is_dir($fn[0]))
			{
				if (!in_array($fn[1], $this->dirs))
				{
					$this->AddDir($fn[1]);
					$ret[] = array($fn[0], $fn[1]);
				}
			}
			elseif (is_file($fn[0]))
			{
				$tempfilesize = @filesize($fn[0]);
				$fp = @fopen($fn[0], 'rb');
				if (!in_array(dirname($fn[1]), $this->dirs))
				{
					$this->AddDir(dirname($fn[1]));
				}
				if (basename($fn[1]))
				{
					$this->AddFile(@fread($fp, $tempfilesize), $fn[1], $intCompact);
					$ret[] = array($fn[0], $fn[1]);
				}
				@fclose($fp);
			}
		}
		return $ret;
	}

	/* 将目录中的文件提取出来用于压缩 */
	private function DirList($strSrcDir, $strDstDir, $intLayer = 0)
	{
		$returnArray = array();
		$intLayer++;
		if ($intLayer > 5)
		{
			return TRUE;
		}
		if (substr($strSrcDir, -1)=='/')
		{
			$strSrcDir = substr($strSrcDir, 0, -1);
		}
		if (substr($strDstDir, -1)=='/')
		{
			$strDstDir = substr($strDstDir, 0, -1);
		}

		$curDir = @opendir($strSrcDir);
		while ($entryname = @readdir($curDir))
		{
			if (is_dir($strSrcDir.'/'.$entryname))
			{
				if ($entryname!='..' && $entryname!='.')
				{
					$this->dirfile[] = array($strSrcDir.'/'.$entryname, $strDstDir.'/'.$entryname);
					$this->DirList($strSrcDir.'/'.$entryname, $strDstDir.'/'.$entryname, $intLayer);
				}
			}
			else
			{
				$this->dirfile[] = array($strSrcDir.'/'.$entryname, $strDstDir.'/'.$entryname);
			}
		}
		@closedir($curDir);
		return TRUE;
	}

	/* 时间换算 */
	private function DosTime()
	{
		$timearray = getdate();
		if ($timearray['year'] < 1980)
		{
			$timearray['year'] = 1980;
			$timearray['mon'] = 1;
			$timearray['mday'] = 1;
			$timearray['hours'] = 0;
			$timearray['minutes'] = 0;
			$timearray['seconds'] = 0;
		}
		return (($timearray['year']-1980) << 25) | ($timearray['mon'] << 21) | ($timearray['mday'] << 16) | ($timearray['hours'] << 11) | 
		($timearray['minutes'] << 5) | ($timearray['seconds'] >> 1);
	}

	/* 解压文件 */
	private function ExtractFile($strToPath, $strZipFile)
	{
		$aryHeader = $this->ReadFileHeader($strZipFile);

		if (isset($aryHeader['external']) && ($aryHeader['external']==0x41FF0010 || $aryHeader['external']==16))
		{
			$curpath = $strToPath.'/'.$aryHeader['filename'];
		}
		else
		{
			$curpath = $strToPath.'/'.dirname($aryHeader['filename']);
		}

		FileSystem::CreateFolder($curpath);
		if (!isset($aryHeader['external']) || ($aryHeader['external']!=0x41FF0010 && $aryHeader['external']!=16))
		{
			if ($aryHeader['compression']==0)
			{
				$fp = @fopen($strToPath.'/'.$aryHeader['filename'], 'wb');
				if (!$fp)
				{
					return FALSE;
				}
				$size = $aryHeader['compressed_size'];

				while ($size!=0)
				{
					$read_size = ($size<2048 ? $size : 2048);
					$buffer = fread($strZipFile, $read_size);
					$binary_data = pack('a'.$read_size, $buffer);
					@fwrite($fp, $binary_data, $read_size);
					$size -= $read_size;
				}
				fclose($fp);
				touch($strToPath.'/'.$aryHeader['filename'], $aryHeader['mtime']);
			}
			else
			{
				$fp = @fopen($strToPath.'/'.$aryHeader['filename'].'.gz', 'wb');
				if (!$fp)
				{
					return FALSE;
				}
				$binary_data = pack('va1a1Va1a1', 0x8b1f, Chr($aryHeader['compression']), Chr(0x00), time(), Chr(0x00), Chr(3));

				fwrite($fp, $binary_data, 10);
				$size = $aryHeader['compressed_size'];

				while ($size!=0)
				{
					$read_size = ($size<1024 ? $size : 1024);
					$buffer = fread($strZipFile, $read_size);
					$binary_data = pack('a'.$read_size, $buffer);
					@fwrite($fp, $binary_data, $read_size);
					$size -= $read_size;
				}

				$binary_data = pack('VV', $aryHeader['crc'], $aryHeader['size']);
				fwrite($fp, $binary_data,8);
				fclose($fp);

				$gzp = @gzopen($strToPath.'/'.$aryHeader['filename'].'.gz', 'rb') or die('Cette archive est compress');
				if (!$gzp)
				{
					return FALSE;
				}
				$fp = @fopen($strToPath.'/'.$aryHeader['filename'], 'wb');
				if (!$fp)
				{
					return FALSE;
				}
				$size = $aryHeader['size'];

				while ($size!=0)
				{
					$read_size = ($size<2048 ? $size : 2048);
					$buffer = gzread($gzp, $read_size);
					$binary_data = pack('a'.$read_size, $buffer);
					@fwrite($fp, $binary_data, $read_size);
					$size -= $read_size;
				}
				fclose($fp);
				gzclose($gzp);

				touch($strToPath.'/'.$aryHeader['filename'], $aryHeader['mtime']);
				@unlink($strToPath.'/'.$aryHeader['filename'].'.gz');
			}
		}
		return TRUE;
	}

	/* 组合文件内容 */
	private function JoinData()
	{
		$data = implode('', $this->datasec);
		$ctrldir = implode('', $this->ctrldir);

		return $data.$ctrldir.$this->eofctrldir.pack('v', sizeof($this->ctrldir)).pack('v', sizeof($this->ctrldir)).pack('V', strlen($ctrldir)).pack('V', strlen($data))."\x00\x00";
	}

	/* 读取中枢路径 */
	private function ReadCentralDir($resZip, $strZipFile)
	{
		$size = filesize($strZipFile);
		@fseek($resZip, $size);

		$vfound = 0;
		if ($size > 26)
		{
			@fseek($resZip, $size-22);
			$pos = @ftell($resZip);
			$data = @unpack('Vid', @fread($resZip, 4));
			if ($data['id'] == 0x06054b50)
			{
				$vfound = 1;
			}
			$pos = ftell($resZip);
		}

		if (!$vfound)
		{
			$maximumSize = 65557;
			if ($maximumSize > $size)
			{
				$maximumSize = $size;
			}
			@fseek($resZip, $size - $maximumSize);
			$pos = ftell($resZip);
			$bytes = 0x00000000;
			while ($pos < $size)
			{
				$byte = @fread($resZip, 1);
				$bytes = ($bytes << 8) | Ord($byte);
				if ($bytes==0x504b0506)
				{
					$pos++;
					break;
				}
				$pos++;
			}
		}

		$data = unpack('vdisk/vdisk_start/vdisk_entries/ventries/Vsize/Voffset/vcomment_size', fread($resZip, 18));
		$centd = array();

		if ($data['comment_size']!=0)
		{
			$centd['comment'] = fread($resZip, $data['comment_size']);
		}
		else
		{
			$centd['comment'] = '';
		}
		$centd['entries'] = $data['entries'];
		$centd['disk_entries'] = $data['disk_entries'];
		$centd['offset'] = $data['offset'];
		$centd['disk_start'] = $data['disk_start'];
		$centd['size'] = $data['size'];
		$centd['disk'] = $data['disk'];
		return $centd;
	}

	/* 读取中枢文件头部 */
	private function ReadCentralFileHeaders($resZip)
	{
		$binary_data = fread($resZip, 46);
		$header = unpack('vchkid/vid/vversion/vversion_extracted/vflag/vcompression/vmtime/vmdate/Vcrc/Vcompressed_size/Vsize/vfilename_len/vextra_len/vcomment_len/vdisk/vinternal/Vexternal/Voffset', $binary_data);

		if ($header['filename_len']!=0)
		{
			$header['filename'] = fread($resZip, $header['filename_len']);
		}
		else
		{
			$header['filename'] = '';
		}

		if ($header['extra_len']!=0)
		{
			$header['extra'] = fread($resZip, $header['extra_len']);
		}
		else
		{
			$header['extra'] = '';
		}

		if ($header['comment_len']!=0)
		{
			$header['comment'] = fread($resZip, $header['comment_len']);
		}
		else
		{
			$header['comment'] = '';
		}

		if ($header['mdate'] && $header['mtime'])
		{
			$hour = ($header['mtime'] & 0xF800) >> 11;
			$minute = ($header['mtime'] & 0x07E0) >> 5;
			$seconde = ($header['mtime'] & 0x001F) * 2;
			$year = (($header['mdate'] & 0xFE00) >> 9) + 1980;
			$month = ($header['mdate'] & 0x01E0) >> 5;
			$day = $header['mdate'] & 0x001F;
			$header['mtime'] = mktime($hour, $minute, $seconde, $month, $day, $year);
		}
		else
		{
			$header['mtime'] = time();
		}
		$header['stored_filename'] = $header['filename'];
		$header['status'] = 'ok';
		if (substr($header['filename'], -1)=='/')
		{
			$header['external'] = 0x41FF0010;
		}
		return $header;
	}

	/* 读取文件头部 */
	private function ReadFileHeader($resZip)
	{
		$binary_data = fread($resZip, 30);
		$data = unpack('vchk/vid/vversion/vflag/vcompression/vmtime/vmdate/Vcrc/Vcompressed_size/Vsize/vfilename_len/vextra_len', $binary_data);
		$header = array();

		$header['filename'] = fread($resZip, $data['filename_len']);
		if ($data['extra_len']!=0)
		{
			$header['extra'] = fread($resZip, $data['extra_len']);
		}
		else
		{
			$header['extra'] = '';
		}

		$header['compression'] = $data['compression'];
		$header['size'] = $data['size'];
		$header['compressed_size'] = $data['compressed_size'];
		$header['crc'] = $data['crc'];
		$header['flag'] = $data['flag'];
		$header['mdate'] = $data['mdate'];
		$header['mtime'] = $data['mtime'];

		if ($header['mdate'] && $header['mtime'])
		{
			$hour = ($header['mtime'] & 0xF800) >> 11;
			$minute = ($header['mtime'] & 0x07E0) >> 5;
			$seconde = ($header['mtime'] & 0x001F) * 2;
			$year = (($header['mdate'] & 0xFE00) >> 9) + 1980;
			$month = ($header['mdate'] & 0x01E0) >> 5;
			$day = $header['mdate'] & 0x001F;
			$header['mtime'] = mktime($hour, $minute, $seconde, $month, $day, $year);
		}
		else
		{
			$header['mtime'] = time();
		}

		$header['stored_filename'] = $header['filename'];
		$header['status'] = 'ok';
		if (substr($header['filename'], -1)=='/')
		{
			$header['external'] = 0x41FF0010;
		}
		return $header;
	}

}	//end class

?>